#pragma once
#include <iostream>
#include <string>
#include <vector>
#include <mutex>
#include <fstream>
#include <memory>
#include <jsoncpp/json/json.h>

#include "../comm/util.hpp"
#include "../comm/log.hpp"
//#include "oj_model.hpp"
#include "oj_view.hpp"
#include "../comm/httplib.h"
#include "./oj_model_mysql.hpp"

namespace ns_control
{

    using namespace ns_log;
    using namespace ns_util;
    //using namespace ns_model;
    using namespace ns_model_mysql;
    using namespace ns_view;
    using namespace httplib;

    // 对服务端机器的描述
    struct Machine
    {
        std::string _ip; // 机器的ip
        int _port;       // 机器进程的端口号
        // std::atomic<int64_t> _loadcount; // 负载情况
        //  为什么这里需要mutex，因为我们的机器可能在多线程中处理请求，对于_loadcount会出现线程安全问题
        //   为了方便我们采用原子类型 —> 因为后面的类中我们采用了拷贝。例如vector中的push_back等。不可避免拷贝
        int64_t _loadcount;
        std::unique_ptr<std::mutex> _mutex; // 为了拷贝采用指针的形式，智能指针unique_ptr
        // 为什么？因为我们每一个对应的管理Machine都应该属于自己唯一的一个互斥量
        // 因为Machine都在vector中被管理着
        // 如果mutex可以是shared_ptr，那么同一个互斥量可能会被除了vector中管理的Machine之外的使用
        // 我们只有通过移动构造了，才能构造对象
        // 或者通过返回最后一个元素引用
    public:
        Machine()
            : _ip(""), _port(0), _loadcount(0), _mutex(nullptr)
        {
        }
        // 删除拷贝操作
        Machine(const Machine &) = delete;
        Machine &operator=(const Machine &) = delete;

        // 默认移动操作
        Machine(Machine &&) = default;
        Machine &operator=(Machine &&) = default;

        ~Machine() = default; // unique_ptr 会自动释放内存
    public:
        int64_t GetLoadCount()
        {
            std::lock_guard<std::mutex>(*_mutex);
            return _loadcount;
        }
        void ReduceLoadCount() // 减少
        {
            if (_mutex)
                _mutex->lock();
            --_loadcount;
            if (_mutex)
                _mutex->unlock();
        }
        void IncreaseLoadCount() // 增加
        {
            if (_mutex)
                _mutex->lock();
            ++_loadcount;
            if (_mutex)
                _mutex->unlock();
        }
        // 重置负载计数
        void ResetLoadCount(int64_t value = 0) // 重置负载均衡器
        {
            if (_mutex)
                _mutex->lock();
            _loadcount = value;
            if (_mutex)
                _mutex->unlock();
        }
    };

    // 对服务端机器的管理：
    // 实现负载均衡
    class MachineManner
    {
    private:
        /************
         * 我们这里采用vector管理机器：出于以下几点考虑：
         * 1. 我们的机器数量不多，较为固定，没有过多的插入删除操作
         * 2. 我们需要让我们的机器，负载均衡，需要对机器管理进行轮询。不选择用优先级队列，因为更新开销、线程安全复杂……
         ************/
        // id作为主机下标
        std::vector<Machine> _manner; // 管理主机
        std::vector<int> _online;     // 维护在线主机
        std::vector<int> _offline;    // 维护离线主机
        std::mutex _mutex;            // 保证操作互斥

        static const std::string conffile;

    public:
        MachineManner()
        {
            if (LoadConfFile(conffile)) // 加载配置文件
            {
                // 这里做一个对机器健康做检测
                AutoCheck();
                LOG(Info, "主机配置文件[%s]读取成功", conffile.c_str());
            }
            else
            {
                LOG(Fatal, "主机配置文件[%s]读取失败!", conffile.c_str());
            }
        }
        ~MachineManner() = default;

    private:
        /******
         * 实现读取配置文件, 对机器进行管理
         ******/
        bool LoadConfFile(const std::string &file) // 这个函数我们基本上只会被调用一次，所以就不加锁了
        {
            // 读取文件
            std::ifstream ifs(file);
            if (!ifs.is_open())
            {
                LOG(Waring, "打开主机配置文件失败");
                return false;
            }
            std::string line; // 一行的内容
            while (std::getline(ifs, line))
            {
                // 获取到一行的内容
                // ip port对读取到的内容进行字符串切割
                std::vector<std::string> content;
                StringUtil::SplitString(line, &content, " ");
                // 获取到了内容 ip port
                //  先对内容进行检查
                if (content.size() != 2)
                {
                    continue;
                }
                Machine m;
                m._ip = content[0];
                m._port = std::stoi(content[1]);
                m._mutex = std::make_unique<std::mutex>();

                // 进入管理
                _offline.push_back(_manner.size()); // 进入在线状态。我们读取配置文件默认是主机在线的
                _manner.push_back(std::move(m));    // 必须进行move，因为unique_ptr不可复制
                                                    //  C++11支持vector的移动元素的操作也是移动赋值！

                // 清空一下字符串
            }
            ifs.close();
            return !ifs.bad(); // 检查读取过程是否出错（排除EOF正常结束的情况）
        }

    public:
        /******
         * 负载均衡式智能选择一台主机
         * 参数：
         * 1. id输出型参数
         * 2. m指针输出型参数
         ********/
        bool IntelChoice(int *id, Machine **m)
        {
            // 保证操作是数据安全的
            _mutex.lock();
            // 遍历我的在线机器
            int onsize = _online.size();
            if (onsize == 0)
            {
                // 说明所以主机已经下线了
                _mutex.unlock();
                LOG(Fatal, "所有主机已经下线了，请检查当前的主机情况");
                return false;
            }
            // 说明有主机
            //  下面实现负载均衡：1、随机数法 + hash 2、轮询（我们采用负载情况）
            *id = _online[0];
            *m = &_manner[_online[0]];
            int64_t minload = _manner[_online[0]].GetLoadCount(); // 得到第一台主机的负载数
            for (int i = 1; i < onsize; ++i)
            {
                int64_t curload = _manner[_online[i]].GetLoadCount();
                if (curload < minload)
                {
                    *id = _online[i];
                    *m = &_manner[_online[i]];
                    minload = curload;
                }
            }

            // 已经选择出来负载最小的机器
            _mutex.unlock();
            LOG(Info, "选择主机: %d, ip: %s:%d, 当前主机负载: %d", *id, (*m)->_ip.c_str(), (*m)->_port, (*m)->_loadcount);
            return true;
        }
        /******
         *  主机的上线功能
         *  我们采用定时发送请求，然后查看是否得到响应来检测主机是否存在
         *****/
    private:
        // 用于检测当前机器的状态
        void CheckLoop()
        {
            // 让线程等20s
            while (true)
            {
                std::this_thread::sleep_for(std::chrono::seconds(20)); // 20s检查一次
                AutoCheck();
            }
        }
        // 检测方式
        void AutoCheck()
        {
            std::lock_guard<std::mutex> lock(_mutex);
            auto iter = _offline.begin();
            while (iter != _offline.end())
            {
                int id = *iter;
                //std::cout << "试图连接主机: " << id << " " << _manner[id]._ip << ":" << _manner[id]._port << std::endl;
                LOG(Debug, "试图连接主机 id:%d %s:%d", id, _manner[id]._ip.c_str(), _manner[id]._port);
                // 拿到离线主机的ID了, 先检查id有效性
                if (id < 0 || id >= _manner.size())
                {
                    std::cout << "Invalid ID: " << id << ", removing" << std::endl;
                    iter = _offline.erase(iter);
                    continue;
                }
                //  构建请求
                Client client(_manner[id]._ip, _manner[id]._port);
                // 防止连接阻塞
                client.set_connection_timeout(2); // 2秒连接超时
                client.set_read_timeout(2);       // 2秒读取超时
                if (auto res = client.Post("/checkonline", "OK?", "text/plain"))
                {
                    if (res->status == 200)
                    {
                        // 访问成功就说明我们的请求是有效的
                        iter = _offline.erase(iter);
                        _online.push_back(id);
                        //std::cout << "连接成功 " << _manner[id]._ip << ":" << _manner[id]._port << std::endl;
                        LOG(Debug, "连接成功 %s:%d", _manner[id]._ip.c_str(), _manner[id]._port);
                        continue; // 跳过++iter
                    }
                }
                //std::cout << "连接失败" << std::endl;
                LOG(Debug, "连接失败 %s:%d", _manner[id]._ip.c_str(), _manner[id]._port);
                ++iter;
            }
        }

    public:
        void OnlineMachine()
        {
            CheckLoop();
        }
        /****
         * 主机的下线功能
         *****/
        bool OfflineMachine(int id)
        {
            _mutex.lock();
            for (auto iter = _online.begin(); iter < _online.end(); ++iter)
            {
                if (*iter == id)
                {
                    // 在在线列表中删除对应的机器
                    //std::cout << "现在转移下线的机器 " << id << " " << _manner[id]._ip << ":" << _manner[id]._port << std::endl;
                    LOG(Waring, "现在转移下线的机器 %d %s:%d", id, _manner[id]._ip.c_str(), _manner[id]._port);
                    _manner[id].ResetLoadCount(0); // 重置负载数
                    _online.erase(iter); //迭代器失效了
                    // 迭代器失效
                    _offline.push_back(id); // 将该机器加入离线列表
                    //std::cout << "check:" << std::endl;
                    //ShowMachine(); // for debug
                    _mutex.unlock();
                    return true;
                }
            }
            _mutex.unlock();
            LOG(Error, "不存在的主机: %d", id);
            return false;
        }
        void ShowMachine() // for debug
        {
            // 1、打印在线主机
            std::cout << "在线主机: " << std::endl;
            for (int i = 0; i < _online.size(); ++i)
            {
                std::cout << _online[i] << " " << _manner[_online[i]]._ip << " " << _manner[_online[i]]._port << std::endl;
            }
            // 2、打印离线主机
            std::cout << "离线主机: " << std::endl;
            for (int i = 0; i < _offline.size(); ++i)
            {
                std::cout << _offline[i] << " " << _manner[_offline[i]]._ip << " " << _manner[_offline[i]]._port << std::endl;
            }
        }
    };
    const std::string MachineManner::conffile = "./machine.conf/conf.txt";

    class Control
    {
    private:
        Model _model;
        View _view;
        MachineManner _loadbalance;

    public:
        Control() = default;
        ~Control() = default;

    public:
        /******唤醒主机** */
        void RecoveryMachine()
        {
            _loadbalance.OnlineMachine();
        }
        // 根据题目构建网页
        bool AllQuestions(std::string *html)
        {
            bool ret = true;
            std::vector<Question> question;
            if (_model.GetAllQuestions(&question))
            {
                // 说明加载成功
                LOG(Info, "获取题目列表成功");
                // 开始对网页进行渲染：
                _view.AllExpandHtml(question, html);
            }
            else
            {
                LOG(Warning, "获取题目列表失败");
                ret = false;
            }
            return ret;
        }
        /*********
         *  构建一个题目的网页
         *******/
        bool OneQuestion(const std::string &number, std::string *html)
        {
            bool ret = true;
            Question q;
            if (_model.GetOneQuestions(number, &q))
            {
                // 开始对网页进行渲染：
                _view.OneExpandHtml(q, html);
            }
            else
            {
                LOG(Error, "获取题目 %s 失败, 该题目不存在", number.c_str());
                ret = false;
            }
            return ret;
        }
        /********
         * 功能：实现判题功能
         * 参数说明：
         * 1. number 题号
         * 2. in_json 客户提交的请求 code input
         * 3. out_json 返回给用户的请求
         *********/
        void JudgeQuestion(const std::string &number, const std::string &in_json, std::string *out_json)
        {
            // 1. 利用题目号，得到对应的内容请求
            Question q;
            _model.GetOneQuestions(number, &q);
            // 拿到了题目题目内容

            // 2. 对客户提交的json串进行反序列化
            Json::Value in_value;
            Json::Reader json_reader;
            json_reader.parse(in_json, in_value); // 把值拿出来

            std::string user_code = in_value["code"].asString();
            std::string user_input = in_value["input"].asString();
            int cpu_limit = q.cpulimit;
            int mem_limit = q.memlimit;
            // 细节：处理user的代码
            std::string code = user_code + "\n" + q.tail; // 完成代码的拼接。添加'\n'保证条件编译不影响

            // 3.构建给compile_and_run的json串
            // {"code":"代码";"input":"提交的样例";"cpulimit":"CPU限制资源";"memlimit":"内存限制资源"}
            Json::Value compile_value;
            compile_value["code"] = code;
            compile_value["input"] = user_input;
            compile_value["cpulimit"] = cpu_limit;
            compile_value["memlimit"] = mem_limit;
            Json::StyledWriter json_writer;
            std::string compile_json = json_writer.write(compile_value); // 构建服务端json

            // 4. 选择对应的链接主机 + 构建请求发送
            while (true)
            {
                int id = -1;
                Machine *m = nullptr;
                if (!_loadbalance.IntelChoice(&id, &m))
                {
                    // 选择主机失败
                    //  这属于服务端的失误，我们不应该把错误给用户暴露出来
                    break;
                }
                // 找到了对应的主机
                m->IncreaseLoadCount(); // 负载数+1
                // 日志已经在找到的逻辑中处理了
                // 构建对compile_and_run的请求
                Client client(m->_ip, m->_port);
                if (auto res = client.Post("/compile_and_run", compile_json, "application/json;charset=utf-8"))
                {
                    // 返回一个Result
                    if (res->status == 200) // 我们的访问是成功的
                    {
                        *out_json = res->body;
                        m->ReduceLoadCount(); // 负载数-1
                        break;                // 直接退出
                    }
                    // 说明访问是错误的，我们重新选择主机进行访问
                    m->ReduceLoadCount(); // 负载数-1
                }
                else
                {
                    // 说明我们的请求是失败的，主机可能退出了
                    LOG(Warning, "主机: %d, ip:port: %s:%d, 已经离线", id, m->_ip.c_str(), m->_port);
                    _loadbalance.OfflineMachine(id); // 将主机设置为离线
                    //_loadbalance.ShowMachine();      // 为了测试
                }
            }
        }
    };
}
